/**
 * File    : OpenID4JavaProvider.java
 * Created : 2008-10-5
 * By      : LuHuiguo
 */
package com.luhuiguo.sso.provider;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.openid4java.message.AuthSuccess;
import org.openid4java.message.DirectError;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.openid4java.server.ServerManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.luhuiguo.sso.service.OpenIDAuthenticationManager;
import com.luhuiguo.sso.service.PropertyManager;
import com.luhuiguo.sso.service.UserManager;

/**
 * @author LuHuiguo
 * @version $Id: OpenID4JavaProvider.java 11 2008-10-15 13:26:26Z LuHuiguo $
 * 
 */
public class OpenID4JavaProvider implements OpenIDProvider {

	private static Logger logger = LoggerFactory
			.getLogger(OpenID4JavaProvider.class);

	private ServerManager serverManager;

	private PropertyManager propertyManager;

	private OpenIDAuthenticationManager openIDAuthenticationManager;

	private String userInteractionURL;

	public void setServerManager(ServerManager serverManager) {
		this.serverManager = serverManager;
	}

	public void setPropertyManager(PropertyManager propertyManager) {
		this.propertyManager = propertyManager;
	}

	public void setOpenIDAuthenticationManager(
			OpenIDAuthenticationManager openIDAuthenticationManager) {
		this.openIDAuthenticationManager = openIDAuthenticationManager;
	}

	/**
	 * @return the userInteractionURL
	 */
	public String getUserInteractionURL() {
		return userInteractionURL;
	}

	/**
	 * @param userInteractionURL
	 *            the userInteractionURL to set
	 */
	public void setUserInteractionURL(String userInteractionURL) {
		this.userInteractionURL = userInteractionURL;
	}

	@Override
	public void processOpenIDRequest(HttpServletRequest request,
			HttpServletResponse response) throws IOException, ServletException,
			OpenIDException {
		ParameterList requestParameters = new ParameterList(request
				.getParameterMap());
		String mode = requestParameters.hasParameter("openid.mode") ? requestParameters
				.getParameterValue("openid.mode")
				: null;
		if (logger.isInfoEnabled()) {
			logger.info((new StringBuilder()).append(
					"Servicing OpenID request, mode = ").append(mode)
					.toString());
		}
		if ("associate".equals(mode)) {
			associate(request, response, requestParameters);
		} else if ("checkid_setup".equals(mode)) {
			checkAuthentication(request, response, requestParameters);
		} else if ("checkid_immediate".equals(mode)) {
			if (propertyManager.isEnableCheckImmediateMode()) {
				checkImmediateAuthentication(request, response,
						requestParameters);
			} else {
				if (logger.isWarnEnabled()) {

					logger
							.warn((new StringBuilder())
									.append(
											"checkid_immediate mode is disabled but has been requested by ")
									.append(request.getRequestURL().toString())
									.toString());
					logger.warn("Proceeding to use checkid_setup mode");
				}
				checkAuthentication(request, response, requestParameters);
			}
		} else if ("check_authentication".equals(mode)) {
			if (propertyManager.isEnableStatelessMode()) {
				verifyAuthentication(request, response, requestParameters);
			} else {
				if (logger.isWarnEnabled()) {
					logger
							.warn((new StringBuilder())
									.append(
											"check_authentication (stateless-mode) is disabled but has been requested by ")
									.append(request.getRequestURL().toString())
									.toString());
					logger.warn("Returning an immediate error response");
				}
				Message error = DirectError.createDirectError(
						"Stateless mode is blocked by this OpenID Provider",
						true);
				sendRPDirectResponse(request, response, error
						.keyValueFormEncoding());
			}
		} else {

			throw new OpenIDException(
					(new StringBuilder())
							.append(
									"OpenID Server trying to process message with an unknown or unspecified openid.mode: ")
							.append(mode).toString());
		}
	}

	protected void sendRedirect(HttpServletRequest request,
			HttpServletResponse response, String destination)
			throws IOException {
		response.sendRedirect(destination);
	}

	protected void sendRPRedirect(HttpServletRequest request,
			HttpServletResponse response, String destination)
			throws IOException {
		removeOpenIDAuthRequest(request.getSession());
		sendRedirect(request, response, destination);
	}

	protected void sendRPDirectResponse(HttpServletRequest request,
			HttpServletResponse response, String responseString)
			throws IOException {
		ServletOutputStream os = response.getOutputStream();
		os.write(responseString.getBytes());
		os.close();
	}

	protected void removeOpenIDAuthRequest(HttpSession session) {
		session
				.setAttribute(OpenIDProvider.OPENID_AUTHENTICATION_REQUEST,
						null);
	}

	@Override
	public void associate(HttpServletRequest request,
			HttpServletResponse response, ParameterList requestParameters)
			throws IOException {
		Message associationResponse = serverManager
				.associationResponse(requestParameters);
		sendRPDirectResponse(request, response, associationResponse
				.keyValueFormEncoding());

	}

	@Override
	public void checkAuthentication(HttpServletRequest request,
			HttpServletResponse response, ParameterList requestParameters)
			throws IOException, OpenIDException {
		try {
			OpenIDAuthRequest authReq = new OpenIDAuthRequest(
					requestParameters, serverManager.getRealmVerifier());
			request.getSession().setAttribute(OPENID_AUTHENTICATION_REQUEST,
					authReq);
		} catch (MalformedOpenIDRequestException e) {
			logger
					.error(
							"Could not generate authentication request message from request paramters",
							e);
			throw new OpenIDException(
					"Could not generate authentication request message from request paramters",
					e);
		}
		sendRedirect(request, response, userInteractionURL);

	}

	@Override
	public void checkImmediateAuthentication(HttpServletRequest request,
			HttpServletResponse response, ParameterList requestParameters)
			throws IOException, OpenIDException {
		OpenIDAuthRequest authReq;
		try {
			authReq = new OpenIDAuthRequest(requestParameters, serverManager
					.getRealmVerifier());
			request.getSession().setAttribute(OPENID_AUTHENTICATION_REQUEST,
					authReq);
		} catch (MalformedOpenIDRequestException e) {
			logger
					.error(
							"Could not generate authentication request message from request paramters",
							e);
			throw new OpenIDException(
					"Could not generate authentication request message from request paramters",
					e);
		}

		String username = getPrincipal(request);

		OpenIDAuthResponse authResp;
		if (username != null) {
			authResp = openIDAuthenticationManager.checkImmediate(username,
					authReq);
		} else {
			logger.info("User not authenticated for immediate request");
			authResp = new OpenIDAuthResponse(authReq.getIdentifier(), false);
		}
		sendAuthenticationResponse(request, response, authResp);

	}

	public String getPrincipal(HttpServletRequest request) {
		return "admin";
	}

	@Override
	public void sendAuthenticationResponse(HttpServletRequest request,
			HttpServletResponse response, OpenIDAuthResponse authResp)
			throws IOException {
		OpenIDAuthRequest authReq = (OpenIDAuthRequest) request.getSession()
				.getAttribute(OPENID_AUTHENTICATION_REQUEST);
		ParameterList requestParameters = authReq.getParamaterList();
		Message authResponseMessage = serverManager.authResponse(
				requestParameters, authResp.getIdentifier(), authResp
						.getClaimedIdentifier(), authResp.isAuthenticated());
		org.openid4java.message.ax.FetchRequest fetchReq = authReq
				.getFetchRequest();
		if (fetchReq != null && (authResponseMessage instanceof AuthSuccess)) {
			logger.debug("Preparing AX response");
			FetchResponse fetchResp = FetchResponse.createFetchResponse(
					fetchReq, authResp.getAttributes());
			if (fetchResp != null)
				try {
					authResponseMessage.addExtension(fetchResp);
				} catch (MessageException e) {
					logger
							.error(
									"Could not add FetchResponse AX extension to successful AuthResponse",
									e);
				}
		}

		SRegRequest sregReq = authReq.getSRegRequest();
		if (sregReq != null && (authResponseMessage instanceof AuthSuccess)) {
			AuthSuccess authSuccessMessage = (AuthSuccess) authResponseMessage;
			logger.debug("Preparing SREG response");
			try {
				SRegResponse sregResp = SRegResponse.createSRegResponse(
						sregReq, authResp.getAttributes());
				authSuccessMessage.addExtension(sregResp);
				authSuccessMessage
						.setSignExtension("http://openid.net/sreg/1.0");
				try {
					serverManager.sign(authSuccessMessage);
				} catch (Exception e) {
					logger.error("Unable to sign SREG response", e);
				}
			} catch (MessageException e) {
				logger
						.error(
								"Could not add SregResponse SREG extension to successful AuthResponse",
								e);
			}
		}
		if (authResponseMessage instanceof DirectError) {
			sendRedirect(request, response, authResponseMessage
					.keyValueFormEncoding());
		} else {
			String destination = authResponseMessage.getDestinationUrl(true);
			sendRPRedirect(request, response, destination);
		}

	}

	@Override
	public void verifyAuthentication(HttpServletRequest request,
			HttpServletResponse response, ParameterList requestParameters)
			throws IOException {
		Message verificationMessage = serverManager.verify(requestParameters);
		sendRPDirectResponse(request, response, verificationMessage
				.keyValueFormEncoding());
	}

}
